﻿#include "asycipc/config_service.hpp"

#include <fstream>
#include <sstream>

namespace asycipc {

namespace {
std::vector<std::string> SplitPath(const std::string& path) {
  std::vector<std::string> parts;
  std::string current;
  for (char ch : path) {
    if (ch == '.') {
      if (!current.empty()) {
        parts.push_back(current);
        current.clear();
      }
    } else {
      current.push_back(ch);
    }
  }
  if (!current.empty()) {
    parts.push_back(current);
  }
  return parts;
}
}  // namespace

bool ConfigService::LoadFromFile(const std::string& path, std::string* error) {
  std::ifstream input(path, std::ios::in | std::ios::binary);
  if (!input) {
    if (error) {
      *error = "failed to open config file";
    }
    return false;
  }
  std::ostringstream buffer;
  buffer << input.rdbuf();
  std::string err;
  auto json = json11::Json::parse(buffer.str(), err);
  if (!err.empty()) {
    if (error) {
      *error = err;
    }
    return false;
  }
  std::vector<std::string> keys;
  {
    std::lock_guard<std::mutex> lock(mutex_);
    config_path_ = path;
    last_raw_ = buffer.str();
    root_ = json;
    if (root_.type() == json11::Json::OBJECT) {
      for (const auto& kv : root_.object_items()) {
        keys.push_back(kv.first);
      }
    }
  }
  Notify("*");
  for (const auto& key : keys) {
    Notify(key);
  }

  return true;
}

bool ConfigService::ReloadIfChanged(std::string* error) {
  if (config_path_.empty()) {
    if (error) {
      *error = "config path not set";
    }
    return false;
  }
  std::ifstream input(config_path_, std::ios::in | std::ios::binary);
  if (!input) {
    if (error) {
      *error = "failed to open config file";
    }
    return false;
  }
  std::ostringstream buffer;
  buffer << input.rdbuf();
  if (buffer.str() == last_raw_) {
    return false;
  }
  std::string err;
  auto json = json11::Json::parse(buffer.str(), err);
  if (!err.empty()) {
    if (error) {
      *error = err;
    }
    return false;
  }
  std::vector<std::string> keys;
  {
    std::lock_guard<std::mutex> lock(mutex_);
    last_raw_ = buffer.str();
    root_ = json;
    if (root_.type() == json11::Json::OBJECT) {
      for (const auto& kv : root_.object_items()) {
        keys.push_back(kv.first);
      }
    }
  }
  Notify("*");
  for (const auto& key : keys) {
    Notify(key);
  }

  return true;
}

json11::Json ConfigService::ResolvePath(const std::string& path) const {
  std::lock_guard<std::mutex> lock(mutex_);
  if (path.empty()) {
    return root_;
  }
  const auto parts = SplitPath(path);
  json11::Json current = root_;
  for (const auto& part : parts) {
    if (current.type() != json11::Json::OBJECT) {
      return json11::Json();
    }
    const auto& obj = current.object_items();
    auto it = obj.find(part);
    if (it == obj.end()) {
      return json11::Json();
    }
    current = it->second;
  }
  return current;
}

json11::Json ConfigService::Get(const std::string& path) const {
  return ResolvePath(path);
}

int ConfigService::GetInt(const std::string& path, int default_value) const {
  const auto json = ResolvePath(path);
  return json.type() == json11::Json::NUMBER
             ? static_cast<int>(json.number_value())
             : default_value;
}

double ConfigService::GetDouble(const std::string& path,
                                double default_value) const {
  const auto json = ResolvePath(path);
  return json.type() == json11::Json::NUMBER ? json.number_value()
                                            : default_value;
}

bool ConfigService::GetBool(const std::string& path, bool default_value) const {
  const auto json = ResolvePath(path);
  return json.type() == json11::Json::BOOL ? json.bool_value()
                                          : default_value;
}

std::string ConfigService::GetString(const std::string& path,
                                     const std::string& default_value) const {
  const auto json = ResolvePath(path);
  return json.type() == json11::Json::STRING ? json.string_value()
                                            : default_value;
}

void ConfigService::RegisterListener(const std::string& key,
                                     std::function<void()> callback) {
  std::lock_guard<std::mutex> lock(mutex_);
  listeners_.emplace(key, std::move(callback));
}

void ConfigService::Notify(const std::string& key) const {
  std::vector<std::function<void()>> callbacks;
  {
    std::lock_guard<std::mutex> lock(mutex_);
    auto range = listeners_.equal_range(key);
    for (auto it = range.first; it != range.second; ++it) {
      callbacks.push_back(it->second);
    }
    auto all_range = listeners_.equal_range("*");
    for (auto it = all_range.first; it != all_range.second; ++it) {
      callbacks.push_back(it->second);
    }
  }
  for (auto& cb : callbacks) {
    if (cb) {
      cb();
    }
  }
}

}  // namespace asycipc


